// --- index.js (MODIFIED WITH LOGGING SYSTEM V2) ---

import {
    extension_settings,
    renderExtensionTemplateAsync,
} from '../../../extensions.js';
import {
    saveSettingsDebounced,
} from '../../../../script.js';

// --- 插件元数据 ---
const PLUGIN_ID = 'modern-screenshot';
const PLUGIN_NAME = 'modern-screenshot';

// NEW: 全局日志系统核心
const screenshotGlobalLogs = [];

// NEW: 日志UI初始化函数 (V2)
const initLogUI = () => {
    // 1. 注入UI所需的特定样式
    const logPanelStyles = `
        <style id="modern-screenshot-styles">
            #modern-screenshot-log-panel {
                width: 80%;
                max-width: 900px;
                height: 70vh;
                max-height: 700px;
                background-color: #2a2a2a;
                border-radius: 8px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.4);
                display: flex;
                flex-direction: column;
                border: 1px solid #444;
            }
            #modern-screenshot-log-panel .inline-drawer-handle {
                text-align: center;
                padding: 10px;
                background-color: #333;
                color: #eee;
                font-weight: bold;
                border-top-left-radius: 8px;
                border-top-right-radius: 8px;
                cursor: move;
                border-bottom: 1px solid #444;
            }
            #modern-screenshot-log-content {
                flex-grow: 1;
                overflow-y: auto;
                padding: 15px;
                font-family: monospace;
                font-size: 12px;
                color: #ddd;
                line-height: 1.6;
            }
            #modern-screenshot-log-footer {
                padding: 10px;
                background-color: #333;
                border-bottom-left-radius: 8px;
                border-bottom-right-radius: 8px;
                text-align: right;
                border-top: 1px solid #444;
            }
            /* Requirement: Button width auto */
            #modern-screenshot-log-footer .menu_button {
                width: auto;
                margin-left: 8px;
            }
        </style>
    `;
    $('head').append(logPanelStyles);


    // 2. 创建UI元素 (仿照示例脚本)
    // 创建遮罩层
    const $overlay = $('<div/>', { id: 'modern-screenshot-log-overlay' }).css({
        'position': 'fixed',
        'top': '0',
        'left': '0',
        'width': '100%',
        'height': '100%',
        'backgroundColor': 'rgba(0,0,0,0.5)',
        'zIndex': '9998',
        'display': 'none',
        'justify-content': 'center',
        'align-items': 'flex-start', // 定位在顶部
        'padding-top': '100px',    // 顶部偏移量
    });

    // 创建面板
    const panelHtml = `
        <div id="modern-screenshot-log-panel">
            <div class="inline-drawer-handle">${PLUGIN_NAME} Log</div>
            <div id="modern-screenshot-log-content"></div>
            <div id="modern-screenshot-log-footer">
                <button id="modern-screenshot-log-download" class="menu_button">下载日志</button>
                <button id="modern-screenshot-log-close" class="menu_button">关闭UI</button>
            </div>
        </div>
    `;
    const $panel = $(panelHtml);

    // 组合并添加到body
    $overlay.append($panel).appendTo('body');


    // 3. 创建触发按钮并添加到菜单
    const logButton = $('<div id="modern_screenshot_log_button" class="menu_button"><i class="fa-solid fa-camera"></i><span class="menu_button_text">截图日志</span></div>');
    $('#extensionsMenu').append(logButton);

    // 4. 绑定事件处理
    const $logContent = $('#modern-screenshot-log-content');

    const populateLogs = () => {
        $logContent.empty();
        if (screenshotGlobalLogs.length === 0) {
            $logContent.html('<p style="color: #888;">No logs recorded for the current session. Try taking a screenshot.</p>');
            return;
        }
        screenshotGlobalLogs.forEach(log => {
            let color = '#ddd';
            if (log.level === 'error') color = '#ff8a8a';
            if (log.level === 'warn') color = '#ffdca8';
            if (log.level === 'info' && (log.message.includes('success') || log.message.includes('Finished'))) color = '#a8e6a8';

            const dataString = log.data ? `<pre style="white-space: pre-wrap; word-break: break-all; margin-top: 5px; color: #aaa; background-color: rgba(0,0,0,0.2); padding: 5px; border-radius: 4px;">Data: ${JSON.stringify(log.data, null, 2)}</pre>` : '';
            const logElement = $(`<div style="color: ${color}; border-bottom: 1px solid #444; padding-bottom: 5px; margin-bottom: 5px;">
                <span style="color: #888;">[${log.timer}]</span>
                <strong style="text-transform: uppercase; color: #bbb;">[${log.level}]</strong>
                <span>${log.message}</span>
                ${dataString}
            </div>`);
            $logContent.append(logElement);
        });
        $logContent.scrollTop($logContent[0].scrollHeight);
    };

    logButton.on('click', () => {
        populateLogs();
        $overlay.fadeIn(200);
    });

    $('#modern-screenshot-log-close').on('click', () => $overlay.fadeOut(200));
    $overlay.on('click', function(e) { if (e.target === this) $(this).fadeOut(200); });


    $('#modern-screenshot-log-clear').on('click', () => {
        screenshotGlobalLogs.length = 0; // Clear the array
        populateLogs();
    });

    $('#modern-screenshot-log-download').on('click', () => {
        const logText = screenshotGlobalLogs.map(log => {
            const dataString = log.data ? `\n  Data: ${JSON.stringify(log.data, null, 2)}` : '';
            return `[${log.timer}][${log.level.toUpperCase()}] ${log.message}${dataString}`;
        }).join('\n\n');

        const blob = new Blob([logText], { type: 'text/plain;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `modern-screenshot-log-${new Date().toISOString()}.txt`;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(url);
    });

    // Make panel draggable
    $panel.draggable({ handle: '.inline-drawer-handle', containment: 'window' });
};


// MODIFIED: 日志系统 (现在会推送到全局数组)
const captureLogger = {
    log: (message, level = 'info', data = null) => {
        const timer = new Date().toISOString();
        const logEntry = { timer, level, message, data };
        screenshotGlobalLogs.push(logEntry);
        // Limit log size to prevent memory issues
        if (screenshotGlobalLogs.length > 500) {
            screenshotGlobalLogs.shift();
        }
        console[level](`[${timer.split('T')[1].slice(0,12)}][${PLUGIN_NAME}] ${message}`, data || '');
    },
    info: (message, data) => { captureLogger.log(message, 'info', data); },
    warn: (message, data) => { captureLogger.log(message, 'warn', data); },
    error: (message, data) => { captureLogger.log(message, 'error', data); },
};

// --- 默认设置与配置 ---
const defaultSettings = {
    screenshotScale: 1.5,
    imageFormat: 'jpeg',
    imageQuality: 0.92,
    autoInstallButtons: true,
    debugOverlay: false,
};
const config = {
    buttonClass: 'st-screenshot-button',
    chatContentSelector: '#chat',
    messageSelector: '.mes',
};

// --- 性能优化 ---
const OPTIMIZED_STYLE_PROPERTIES = new Set([
    'display', 'position', 'top', 'right', 'bottom', 'left', 'float', 'clear',
    'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',
    'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
    'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
    'border', 'border-width', 'border-style', 'border-color', 'border-radius',
    'border-top-left-radius', 'border-top-right-radius', 'border-bottom-left-radius', 'border-bottom-right-radius',
    'border-collapse', 'border-spacing', 'box-sizing', 'overflow', 'overflow-x', 'overflow-y',
    'flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap',
    'align-content', 'align-items', 'align-self', 'justify-content', 'justify-items', 'justify-self',
    'gap', 'row-gap', 'column-gap',
    'grid', 'grid-area', 'grid-template', 'grid-template-areas', 'grid-template-rows', 'grid-template-columns',
    'grid-row', 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-column-start', 'grid-column-end',
    'color', 'font', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',
    'line-height', 'letter-spacing', 'word-spacing', 'text-align', 'text-decoration', 'text-indent',
    'text-transform', 'text-shadow', 'white-space', 'vertical-align',
    'background', 'background-color', 'background-image', 'background-repeat', 'background-position', 'background-size', 'background-clip',
    'opacity', 'visibility', 'box-shadow', 'outline', 'outline-offset', 'cursor',
    'transform', 'transform-origin', 'transform-style', 'transition', 'animation', 'filter',
    'list-style', 'list-style-type', 'list-style-position', 'list-style-image',
]);
const STYLE_WHITELIST_ARRAY = Array.from(OPTIMIZED_STYLE_PROPERTIES);

let CACHED_UNIT_BACKGROUND = null;
function invalidateUnitBackgroundCache() {
    if (CACHED_UNIT_BACKGROUND) {
        captureLogger.info('Cache invalidation: Unit background has been cleared.');
        CACHED_UNIT_BACKGROUND = null;
    }
}

// --- 混合缓存策略核心 ---
const FONT_DATA_MEMORY_CACHE = new Map();
const IMAGE_DATA_MEMORY_CACHE = new Map();
let ACTIVE_FONT_MAPPING = null;
let CACHED_FA_CSS = null;

class AssetCacheManager {
    constructor(dbName = 'ModernScreenshotCache', version = 1) {
        this.db = null;
        this.dbName = dbName;
        this.dbVersion = 2;
        this.stores = {
            fontMappings: 'fontMappings',
            fontData: 'fontData',
            imageData: 'imageData',
        };
    }
    async init() {
        return new Promise((resolve, reject) => {
            if (this.db) return resolve();
            const request = indexedDB.open(this.dbName, this.dbVersion);
            request.onupgradeneeded = (event) => {
                const db = event.target.result;
                if (!db.objectStoreNames.contains(this.stores.fontMappings)) {
                    db.createObjectStore(this.stores.fontMappings, { keyPath: 'cssUrl' });
                }
                if (!db.objectStoreNames.contains(this.stores.fontData)) {
                    db.createObjectStore(this.stores.fontData, { keyPath: 'fontUrl' });
                }
                if (!db.objectStoreNames.contains(this.stores.imageData)) {
                    db.createObjectStore(this.stores.imageData, { keyPath: 'imageUrl' });
                }
            };
            request.onsuccess = (event) => {
                this.db = event.target.result;
                resolve();
            };
            request.onerror = (event) => {
                captureLogger.error('Failed to connect to asset cache database:', event.target.error);
                reject(event.target.error);
            };
        });
    }
    _getStore(storeName, mode = 'readonly') {
        const transaction = this.db.transaction(storeName, mode);
        return transaction.objectStore(storeName);
    }
    async getAllFontData() {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.fontData);
            const request = store.getAll();
            request.onsuccess = () => resolve(request.result);
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async getMapping(cssUrl) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.fontMappings);
            const request = store.get(cssUrl);
            request.onsuccess = () => resolve(request.result?.mapping);
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async saveMapping(cssUrl, mapping) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.fontMappings, 'readwrite');
            const request = store.put({ cssUrl, mapping });
            request.onsuccess = () => resolve();
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async getFontData(fontUrl) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.fontData);
            const request = store.get(fontUrl);
            request.onsuccess = () => resolve(request.result?.dataUrl);
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async saveFontData(fontUrl, dataUrl) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.fontData, 'readwrite');
            const request = store.put({ fontUrl, dataUrl });
            request.onsuccess = () => resolve();
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async getAllImageData() {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.imageData);
            const request = store.getAll();
            request.onsuccess = () => resolve(request.result);
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async getImageData(imageUrl) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.imageData);
            const request = store.get(imageUrl);
            request.onsuccess = () => resolve(request.result?.dataUrl);
            request.onerror = (e) => reject(e.target.error);
        });
    }
    async saveImageData(imageUrl, dataUrl) {
        return new Promise((resolve, reject) => {
            const store = this._getStore(this.stores.imageData, 'readwrite');
            const request = store.put({ imageUrl, dataUrl });
            request.onsuccess = () => resolve();
            request.onerror = (e) => reject(e.target.error);
        });
    }
	async processFontFromStyleElement() {
		const styleElement = document.querySelector('#custom-style');
		if (!styleElement) {
			captureLogger.warn('Could not find #custom-style element, skipping font processing.');
			return;
		}
		const rawCss = styleElement.textContent || '';
		const importMatch = /@import\s+url\((['"]?)(.*?)\1\);/g.exec(rawCss);
		let cssContent;
		let baseUrl;
		let styleIdentifier;
		if (importMatch) {
			styleIdentifier = importMatch[2];
			baseUrl = styleIdentifier;
			captureLogger.info(`Detected external font CSS: ${styleIdentifier}`);
			if (ACTIVE_FONT_MAPPING && ACTIVE_FONT_MAPPING.cssUrl === styleIdentifier) return;
			const dbMapping = await assetCacheManager.getMapping(styleIdentifier);
			if (dbMapping) {
				ACTIVE_FONT_MAPPING = { cssUrl: styleIdentifier, mapping: dbMapping };
				captureLogger.info(`Font mapping loaded from DB into memory: ${styleIdentifier}`);
				return;
			}
			try {
				cssContent = await fetch(styleIdentifier).then(res => res.text());
			} catch (error) {
				captureLogger.error(`Failed to download external font CSS: ${styleIdentifier}`, error);
				return;
			}
		} else if (rawCss.includes('@font-face')) {
			styleIdentifier = 'inline-style:' + rawCss.trim();
			baseUrl = window.location.href;
			cssContent = rawCss;
			captureLogger.info('Detected inline @font-face rules.');
			if (ACTIVE_FONT_MAPPING && ACTIVE_FONT_MAPPING.cssUrl === styleIdentifier) return;
			const dbMapping = await assetCacheManager.getMapping(styleIdentifier);
			if (dbMapping) {
				ACTIVE_FONT_MAPPING = { cssUrl: styleIdentifier, mapping: dbMapping };
				captureLogger.info(`Font mapping loaded from DB into memory: ${styleIdentifier.substring(0, 70)}...`);
				return;
			}
		} else {
			captureLogger.info('Current theme does not use @import or inline @font-face for fonts.');
			ACTIVE_FONT_MAPPING = null;
			return;
		}
		try {
			captureLogger.info(`Creating font mapping for new style: ${styleIdentifier.substring(0, 70)}...`);
			const fontFaceRegex = /@font-face\s*{([^}]*)}/g;
			const unicodeRangeRegex = /unicode-range:\s*([^;]*);/;
			const urlRegex = /url\((['"]?)(.*?)\1\)/;
			const mapping = {};
			let match;
			fontFaceRegex.lastIndex = 0;
			while ((match = fontFaceRegex.exec(cssContent)) !== null) {
				const fontFaceBlock = match[1];
				const unicodeRangeMatch = fontFaceBlock.match(unicodeRangeRegex);
				const urlMatch = fontFaceBlock.match(urlRegex);
				if (urlMatch) {
					const fontFileUrl = new URL(urlMatch[2], baseUrl).href;
					if (unicodeRangeMatch) {
						const ranges = unicodeRangeMatch[1];
						ranges.split(',').forEach(range => {
							range = range.trim().toUpperCase().substring(2);
							if (range.includes('-')) {
								const [start, end] = range.split('-').map(hex => parseInt(hex, 16));
								for (let i = start; i <= end; i++) { mapping[i] = fontFileUrl; }
							} else {
								mapping[parseInt(range, 16)] = fontFileUrl;
							}
						});
					} else {
						mapping['default'] = fontFileUrl;
					}
				}
			}
			if (Object.keys(mapping).length > 0) {
				await assetCacheManager.saveMapping(styleIdentifier, mapping);
				ACTIVE_FONT_MAPPING = { cssUrl: styleIdentifier, mapping: mapping };
				captureLogger.info(`Font mapping created and saved to DB/memory: ${styleIdentifier.substring(0, 70)}...`);
			} else {
				captureLogger.warn('Found @font-face in styles but could not parse any mappings.');
			}
		} catch (error) {
			captureLogger.error(`Error processing styles: ${styleIdentifier}`, error);
		}
	}
}

const assetCacheManager = new AssetCacheManager();

// --- 资源获取核心 (混合缓存策略) ---
async function getFontDataUrlAsync(fontUrl) {
    if (FONT_DATA_MEMORY_CACHE.has(fontUrl)) return FONT_DATA_MEMORY_CACHE.get(fontUrl);
    let dataUrl = await assetCacheManager.getFontData(fontUrl);
    if (dataUrl) {
        FONT_DATA_MEMORY_CACHE.set(fontUrl, dataUrl);
        return dataUrl;
    }
    captureLogger.info(`Downloading and caching font: ${fontUrl}`);
    try {
        const fontBlob = await fetch(fontUrl).then(res => res.ok ? res.blob() : Promise.reject(`HTTP ${res.status}`));
        dataUrl = await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.onerror = reject;
            reader.readAsDataURL(fontBlob);
        });
        FONT_DATA_MEMORY_CACHE.set(fontUrl, dataUrl);
        await assetCacheManager.saveFontData(fontUrl, dataUrl);
        return dataUrl;
    } catch (err) {
        captureLogger.error(`Failed to download font: ${fontUrl}`, err);
        return null;
    }
}

async function customImageFetchFn(url) {
    captureLogger.info(`Custom fetch initiated for URL: ${url}`);
    if (url.startsWith('data:')) {
        captureLogger.info('URL is a data URL, skipping custom fetch.');
        return false;
    }
    if (IMAGE_DATA_MEMORY_CACHE.has(url)) {
        captureLogger.info('Image found in L1 Memory Cache.', { url });
        return IMAGE_DATA_MEMORY_CACHE.get(url);
    }
    captureLogger.info('Image not in L1 Cache, checking L2 IndexedDB.', { url });
    let dataUrl = await assetCacheManager.getImageData(url);
    if (dataUrl) {
        IMAGE_DATA_MEMORY_CACHE.set(url, dataUrl);
        captureLogger.info('Image found in L2 IndexedDB Cache and populated to L1.', { url });
        return dataUrl;
    }
    captureLogger.warn('Image not in any cache, fetching from network.', { url });
    try {
        const response = await fetch(url, { mode: 'cors' });
        if (!response.ok) {
            const errorData = { url, status: response.status, statusText: response.statusText };
            captureLogger.error('Network fetch failed: Non-2xx response.', errorData);
            return Promise.reject(`HTTP ${response.status} for ${url}`);
        }
        const contentType = response.headers.get('content-type');
        if (!contentType || !contentType.startsWith('image/')) {
            const errorData = { url, contentType };
            captureLogger.error('Network fetch failed: Response is not an image type.', errorData);
            return Promise.reject(`URL is not an image type: ${contentType}`);
        }
        captureLogger.info('Network fetch successful, processing blob.', { url, contentType });
        const imageBlob = await response.blob();
        dataUrl = await new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => resolve(reader.result);
            reader.onerror = reject;
            reader.readAsDataURL(imageBlob);
        });
        IMAGE_DATA_MEMORY_CACHE.set(url, dataUrl);
        await assetCacheManager.saveImageData(url, dataUrl);
        captureLogger.info('Image successfully fetched, converted, and cached in L1/L2.', { url });
        return dataUrl;
    } catch (err) {
        captureLogger.error('CRITICAL: Network fetch threw an exception. This is often a CORS issue on Safari/iOS.', {
            url,
            errorMessage: err.message,
            errorName: err.name,
            errorStack: err.stack,
        });
        return false;
    }
}

async function getFontAwesomeCssAsync() {
    if (CACHED_FA_CSS) {
        captureLogger.info('Using cached Font Awesome CSS from memory.');
        return CACHED_FA_CSS;
    }
    captureLogger.info('Processing Font Awesome @font-face rules...');
    const fontFaceRules = [];
    for (const sheet of document.styleSheets) {
        try {
            if (!sheet.cssRules) continue;
            for (const rule of sheet.cssRules) {
                if (rule.type === CSSRule.FONT_FACE_RULE && rule.style.fontFamily.includes('Font Awesome')) {
                    fontFaceRules.push(rule);
                }
            }
        } catch (e) { continue; }
    }
    if (fontFaceRules.length === 0) {
        captureLogger.warn('Could not find any Font Awesome @font-face rules.');
        return '';
    }
    const fontUrlRegex = /url\((['"]?)(.+?)\1\)/g;
    const processedRulesPromises = fontFaceRules.map(async (rule) => {
        let originalCssText = rule.cssText;
        let processedRule = originalCssText;
        const fontUrlMatches = [...originalCssText.matchAll(fontUrlRegex)];
        for (const urlMatch of fontUrlMatches) {
            const originalUrlToken = urlMatch[0];
            const absoluteFontUrl = new URL(urlMatch[2], rule.parentStyleSheet.href || window.location.href).href;
            const fontDataUrl = await getFontDataUrlAsync(absoluteFontUrl);
            if (fontDataUrl) {
                processedRule = processedRule.replace(originalUrlToken, `url("${fontDataUrl}")`);
            }
        }
        return processedRule;
    });
    const finalRules = await Promise.all(processedRulesPromises);
    CACHED_FA_CSS = finalRules.join('\n');
    captureLogger.info(`Font Awesome CSS processed, ${finalRules.length} rules inlined.`);
    return CACHED_FA_CSS;
}

async function getSubsettedFontCssAsync(text) {
    if (!ACTIVE_FONT_MAPPING) {
        captureLogger.warn('No active font mapping available, cannot generate subsetted font CSS.');
        return '';
    }
    const { cssUrl, mapping } = ACTIVE_FONT_MAPPING;
    const requiredFontUrls = new Set();
    if (mapping['default']) {
        requiredFontUrls.add(mapping['default']);
    }
    for (const char of text) {
        const charCode = char.charCodeAt(0);
        if (mapping[charCode]) {
            requiredFontUrls.add(mapping[charCode]);
        }
    }
    if (requiredFontUrls.size === 0) return '';
    const urlToDataUrlMap = new Map();
    const fetchPromises = [];
    for (const url of requiredFontUrls) {
        const fetchPromise = (async () => {
            const dataUrl = await getFontDataUrlAsync(url);
            if (dataUrl) {
                urlToDataUrlMap.set(url, dataUrl);
            }
        })();
        fetchPromises.push(fetchPromise);
    }
    await Promise.all(fetchPromises);
    let cssContent;
    let baseUrl;
    if (cssUrl.startsWith('inline-style:')) {
        cssContent = cssUrl.substring('inline-style:'.length);
        baseUrl = window.location.href;
    } else {
        cssContent = await fetch(cssUrl).then(res => res.text());
        baseUrl = cssUrl;
    }
    const fontFaceRegex = /@font-face\s*{[^}]*}/g;
    const requiredCssRules = [];
    let match;
    fontFaceRegex.lastIndex = 0;
    while ((match = fontFaceRegex.exec(cssContent)) !== null) {
        const rule = match[0];
        const urlMatch = /url\((['"]?)(.*?)\1\)/.exec(rule);
        if (urlMatch) {
            const fontFileUrl = new URL(urlMatch[2], baseUrl).href;
            if (urlToDataUrlMap.has(fontFileUrl)) {
                requiredCssRules.push(rule.replace(urlMatch[0], `url("${urlToDataUrlMap.get(fontFileUrl)}")`));
            }
        }
    }
    const finalCss = requiredCssRules.join('\n');
    captureLogger.info(`Generated ${requiredCssRules.length} inlined @font-face rules for current text.`);
    return finalCss;
}

// --- 背景与合成核心 (单消息) ---
function findActiveBackgroundElement() {
    const selectors = ['#bg_animation_container > div[id^="bg"]', '#background > div[id^="bg"]', '#bg1', '#bg_animation_container', '#background'];
    for (const selector of selectors) {
        const el = document.querySelector(selector);
        if (el && window.getComputedStyle(el).display !== 'none' && window.getComputedStyle(el).backgroundImage !== 'none') return el;
    }
    captureLogger.warn("Could not find a specific background element, falling back to #chat as source.");
    return document.querySelector(config.chatContentSelector);
}

function loadImage(dataUrl) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.onload = () => resolve(img);
        img.onerror = (err) => reject(new Error('Image load failed', { cause: err }));
        img.src = dataUrl;
    });
}

async function createUnitBackgroundAsync(scale) {
    if (CACHED_UNIT_BACKGROUND) {
        captureLogger.info('Using cached "unit background".');
        const clonedCanvas = CACHED_UNIT_BACKGROUND.cloneNode(true);
        const ctx = clonedCanvas.getContext('2d');
        ctx.drawImage(CACHED_UNIT_BACKGROUND, 0, 0);
        return clonedCanvas;
    }
    captureLogger.info('Creating new tileable "unit background"...');
    const backgroundHolder = findActiveBackgroundElement();
    const chatContainer = document.querySelector(config.chatContentSelector);
    const formSheld = document.querySelector('#form_sheld');
    if (!backgroundHolder || !chatContainer) throw new Error("Cannot find #chat or background element!");
    const chatRect = chatContainer.getBoundingClientRect();
    const formSheldHeight = formSheld ? formSheld.offsetHeight : 0;
    const unitWidth = chatContainer.clientWidth;
    const unitHeight = chatRect.height - formSheldHeight;
    const unitTop = chatRect.top;
    const unitLeft = chatContainer.getBoundingClientRect().left;
    const foregroundSelectors = ['#chat', '#form_sheld', '.header', '#right-panel', '#left-panel', '#character-popup'];
    const hiddenElements = [];
    let fullBackgroundDataUrl;
    try {
        foregroundSelectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(el => {
                if (el.style.visibility !== 'hidden') {
                    el.style.visibility = 'hidden';
                    hiddenElements.push(el);
                }
            });
        });
        await new Promise(resolve => setTimeout(resolve, 100));
        fullBackgroundDataUrl = await window.domToDataUrl(backgroundHolder, {
            scale,
            includeStyleProperties: STYLE_WHITELIST_ARRAY,
            fetchFn: customImageFetchFn,
        });
    } finally {
        hiddenElements.forEach(el => { el.style.visibility = 'visible'; });
    }
    if (!fullBackgroundDataUrl) throw new Error("Background capture failed during unit background creation.");
    const fullBgImage = await loadImage(fullBackgroundDataUrl);
    const unitCanvas = document.createElement('canvas');
    unitCanvas.width = unitWidth * scale;
    unitCanvas.height = unitHeight * scale;
    const unitCtx = unitCanvas.getContext('2d');
    unitCtx.drawImage(fullBgImage, unitLeft * scale, unitTop * scale, unitWidth * scale, unitHeight * scale, 0, 0, unitWidth * scale, unitHeight * scale);
    CACHED_UNIT_BACKGROUND = unitCanvas;
    captureLogger.info('"Unit background" created and cached successfully.');
    const returnedCanvas = unitCanvas.cloneNode(true);
    const returnedCtx = returnedCanvas.getContext('2d');
    returnedCtx.drawImage(unitCanvas, 0, 0);
    return returnedCanvas;
}

// MODIFIED: `captureLongScreenshot` with log clearing
async function captureLongScreenshot(elementsToCapture) {
    // Requirement: Clear previous logs for a clean report
    screenshotGlobalLogs.length = 0;

    captureLogger.log('--- New Screenshot Process Started ---', 'info', {
        userAgent: navigator.userAgent,
        elementCount: elementsToCapture.length,
        isSingleMessage: elementsToCapture.length === 1,
    });

    if (!elementsToCapture || elementsToCapture.length === 0) {
        const error = new Error("No elements provided for long screenshot.");
        captureLogger.error(error.message, { error });
        throw error;
    }

    const timer = (label, start = performance.now()) => () => captureLogger.info(`⏱️ [Performance] ${label}: ${(performance.now() - start).toFixed(2)} ms`);
    const mainProcessStart = timer('Total process');
    const settings = getPluginSettings();
    const scale = settings.screenshotScale;
    const fontPrepStart = timer('0. Font preparation');
    const allTextContent = elementsToCapture.map(el => el.textContent || '').join('');
    const [subsettedCss, faCss] = await Promise.all([
        getSubsettedFontCssAsync(allTextContent),
        getFontAwesomeCssAsync(),
    ]);
    const combinedCss = `${subsettedCss}\n${faCss}`;
    fontPrepStart();
    const calcStart = timer('1. Size calculation');
    let totalHeight = 0;
    let maxWidth = 0;
    elementsToCapture.forEach(el => {
        const rect = el.getBoundingClientRect();
        totalHeight += rect.height;
        if (el.clientWidth > maxWidth) maxWidth = el.clientWidth;
    });
    const messageMargin = elementsToCapture.length > 1 ? 5 : 0;
    totalHeight += (elementsToCapture.length - 1) * messageMargin;
    const finalWidth = maxWidth * scale;
    const finalHeight = totalHeight * scale;
    captureLogger.info(`Final dimensions calculated: ${finalWidth / scale}x${totalHeight} (scaled: ${finalWidth}x${finalHeight})`);
    calcStart();
    const bgPrepStart = timer('2. Background preparation');
    const unitBgCanvas = await createUnitBackgroundAsync(scale);
    const finalCanvas = document.createElement('canvas');
    finalCanvas.width = finalWidth;
    finalCanvas.height = finalHeight;
    const finalCtx = finalCanvas.getContext('2d');
    const pattern = finalCtx.createPattern(unitBgCanvas, 'repeat-y');
    finalCtx.fillStyle = pattern;
    finalCtx.fillRect(0, 0, finalWidth, finalHeight);
    bgPrepStart();
    const chatElement = document.querySelector(config.chatContentSelector);
    if (chatElement) {
        const chatBgColor = window.getComputedStyle(chatElement).backgroundColor;
        if (chatBgColor && chatBgColor !== 'rgba(0, 0, 0, 0)') {
            captureLogger.info(`Applying #chat background color to long screenshot: ${chatBgColor}`);
            finalCtx.fillStyle = chatBgColor;
            finalCtx.fillRect(0, 0, finalWidth, finalHeight);
        }
    }
    const stitchStart = timer('3. Foreground stitching');
    const lib = window.modernScreenshot;
    const context = await lib.createContext(elementsToCapture[0], {
        scale,
        font: false,
        includeStyleProperties: STYLE_WHITELIST_ARRAY,
        style: { margin: '0' },
        features: { restoreScrollPosition: true },
        fetchFn: customImageFetchFn,
        onCreateForeignObjectSvg: (svg) => {
                const quoteFixCss = 'q::before, q::after { content: none !important; }';
                const layoutFixCss = `
                    pre {
                        white-space: pre-wrap !important;
                        word-break: break-all !important;
                        overflow-wrap: break-word !important;
                    }
                    .name_text {
                        white-space: nowrap !important;
                    }
                    .ch_name {
                        letter-spacing: -0.5px !important;
                    }
				`;
                const finalCss = combinedCss + '\n' + quoteFixCss + '\n' + layoutFixCss;
                if (finalCss) {
                    const styleElement = document.createElement('style');
                    styleElement.textContent = finalCss;
                    let defs = svg.querySelector('defs');
                    if (!defs) { defs = document.createElement('defs'); svg.prepend(defs); }
                    defs.appendChild(styleElement);
                }
            },
        workerUrl: `/scripts/extensions/third-party/${PLUGIN_ID}/worker.js`,
        autoDestruct: false,
    });
    let currentY = 0;
    for (const element of elementsToCapture) {
        captureLogger.info(`Processing message part: ${element.getAttribute('mesid') || 'Unknown ID'}`);
        const rect = element.getBoundingClientRect();
        context.node   = element;
        context.width  = rect.width;
        context.height = rect.height;
        const sectionCanvas = await lib.domToCanvas(context);
        const offsetX = (finalWidth - sectionCanvas.width) / 2;
        finalCtx.drawImage(sectionCanvas, offsetX, currentY);
        currentY += rect.height * scale + messageMargin * scale;
    }
    lib.destroyContext(context);
    stitchStart();
    const exportStart = timer('4. Final image export');
    const finalDataUrl = finalCanvas.toDataURL(settings.imageFormat === 'jpeg' ? 'image/jpeg' : 'image/png', settings.imageQuality);
    exportStart();
    mainProcessStart();
    captureLogger.log('--- Screenshot Process Finished Successfully ---', 'info');
    return finalDataUrl;
}


// --- 插件初始化与UI ---
async function loadScript(src) {
    return new Promise((resolve, reject) => {
        if (document.querySelector(`script[src="${src}"]`)) return resolve();
        const script = document.createElement('script');
        script.src = src;
        script.async = true;
        script.onload = resolve;
        script.onerror = () => reject(new Error(`Script load failed: ${src}`));
        document.head.appendChild(script);
    });
}

function getPluginSettings() {
    extension_settings[PLUGIN_ID] = extension_settings[PLUGIN_ID] || {};
    return { ...defaultSettings, ...extension_settings[PLUGIN_ID] };
}

function initLongScreenshotUI() {
    $('#long_screenshot_start_button, #long_screenshot_capture_button, #long_screenshot_cancel_button').remove();
    const startButton = $('<div id="long_screenshot_start_button" class="menu_button"><i class="fa-solid fa-scroll"></i><span class="menu_button_text"> Long Screenshot</span></div>');
    $('#chat_menu_buttons').append(startButton);
    startButton.on('click', () => {
        $('body').addClass('long-screenshot-selecting');
        $('#chat .mes').addClass('selectable-message');
        startButton.hide();
        const captureButton = $('<div id="long_screenshot_capture_button" class="menu_button"><i class="fa-solid fa-camera"></i><span class="menu_button_text"> Capture</span></div>');
        const cancelButton = $('<div id="long_screenshot_cancel_button" class="menu_button"><i class="fa-solid fa-times"></i><span class="menu_button_text"> Cancel</span></div>');
        $('#chat_menu_buttons').append(captureButton, cancelButton);
        cancelButton.on('click', () => {
            $('body').removeClass('long-screenshot-selecting');
            $('#chat .mes').removeClass('selectable-message selected-for-screenshot');
            captureButton.remove();
            cancelButton.remove();
            startButton.show();
        });
        captureButton.on('click', async () => {
            const selectedElements = Array.from(document.querySelectorAll('.selected-for-screenshot'));
            if (selectedElements.length === 0) {
                toastr.warning("Please select at least one message.");
                return;
            }
            selectedElements.sort((a, b) => a.getBoundingClientRect().top - b.getBoundingClientRect().top);
            const icon = captureButton.find('i');
            const originalClass = icon.attr('class');
            icon.attr('class', 'fa-solid fa-spinner fa-spin');
            try {
                const dataUrl = await captureLongScreenshot(selectedElements);
                const link = document.createElement('a');
                const extension = dataUrl.substring('data:image/'.length, dataUrl.indexOf(';'));
                link.download = `SillyTavern_Long_${new Date().toISOString().replace(/[:.T-]/g, '').slice(0, 14)}.${extension}`;
                link.href = dataUrl;
                link.click();
            } catch (error) {
                captureLogger.error('Long screenshot failed:', error);
                toastr.error("Long screenshot failed. Please check the console for more information.");
            } finally {
                icon.attr('class', originalClass);
                cancelButton.trigger('click');
            }
        });
    });
    $(document).on('click', '.selectable-message', function() { $(this).toggleClass('selected-for-screenshot'); });
    const styles = `
        .long-screenshot-selecting #chat { cursor: pointer; }
        .selectable-message { transition: background-color 0.2s; }
        .selected-for-screenshot { background-color: rgba(0, 150, 255, 0.3) !important; }
    `;
    $('head').append(`<style>${styles}</style>`);
}

function addScreenshotButtonToMessage(messageElement) {
    if (!messageElement || typeof messageElement.querySelector !== 'function' || messageElement.querySelector(`.${config.buttonClass}`)) return;
    const buttonsContainer = messageElement.querySelector('.mes_block .mes_buttons');
    if (!buttonsContainer) return;
    const screenshotButton = document.createElement('div');
    screenshotButton.innerHTML = '<i class="fa-solid fa-camera"></i>';
    screenshotButton.className = `${config.buttonClass} mes_button interactable`;
    screenshotButton.title = 'Take a screenshot';
    Object.assign(screenshotButton.style, {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        cursor: 'pointer',
    });
    screenshotButton.addEventListener('click', async (event) => {
        event.preventDefault();
        event.stopPropagation();
        if (screenshotButton.classList.contains('loading')) return;
        const icon = screenshotButton.querySelector('i');
        const originalClass = icon.className;
        icon.className = 'fa-solid fa-spinner fa-spin';
        screenshotButton.classList.add('loading');
        try {
            captureLogger.info('Executing single message screenshot (unified optimization mode)...');
            const dataUrl = await captureLongScreenshot([messageElement]);
            const link = document.createElement('a');
            const extension = dataUrl.substring('data:image/'.length, dataUrl.indexOf(';'));
            link.download = `SillyTavern_${new Date().toISOString().replace(/[:.T-]/g, '').slice(0, 14)}.${extension}`;
            link.href = dataUrl;
            link.click();
        } catch (error) {
            captureLogger.error('Message screenshot failed:', error);
            toastr.error('Screenshot failed. Please check the console for more information.');
        } finally {
            icon.className = originalClass;
            screenshotButton.classList.remove('loading');
        }
    });
    const extraButtonsContainer = buttonsContainer.querySelector('.extraMesButtons');
    if (extraButtonsContainer) {
        buttonsContainer.insertBefore(screenshotButton, extraButtonsContainer);
    } else {
        buttonsContainer.appendChild(screenshotButton);
    }
}

function installScreenshotButtons() {
    document.querySelectorAll(`.${config.buttonClass}`).forEach(btn => btn.remove());
    const chatContentEl = document.querySelector(config.chatContentSelector);
    if (!chatContentEl) {
        captureLogger.warn('Chat container not found, retrying in 1s...');
        setTimeout(installScreenshotButtons, 1000);
        return;
    }
    chatContentEl.querySelectorAll(config.messageSelector).forEach(addScreenshotButtonToMessage);
    const observer = new MutationObserver(mutations => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node.nodeType === 1) {
                    if (node.matches(config.messageSelector)) {
                        addScreenshotButtonToMessage(node);
                    } else if (typeof node.querySelectorAll === 'function') {
                        node.querySelectorAll(config.messageSelector).forEach(addScreenshotButtonToMessage);
                    }
                }
            }
        }
    });
    observer.observe(chatContentEl, { childList: true, subtree: true });
}

function setupFontChangeObserver() {
    const styleNode = document.getElementById('custom-style');
    if (!styleNode) {
        captureLogger.warn('Could not find #custom-style element, cannot set up font change observer.');
        return;
    }
    const observer = new MutationObserver(() => {
        captureLogger.info('#custom-style content changed, processing new fonts...');
        assetCacheManager.processFontFromStyleElement().catch(err => {
            captureLogger.error("Failed to re-preprocess font mapping:", err);
        });
    });
    observer.observe(styleNode, { childList: true, characterData: true, subtree: true });
    captureLogger.info('Font change observer for #custom-style has been successfully set up.');
}

async function initializePlugin() {
    try {
        captureLogger.info('Plugin core initialization starting...');
        const libPromise = loadScript(`/scripts/extensions/third-party/${PLUGIN_ID}/modern-screenshot.umd.js`);
        const dbInitPromise = assetCacheManager.init();
        await Promise.all([libPromise, dbInitPromise]);
        if (!window.modernScreenshot?.domToDataUrl) throw new Error('Modern Screenshot library failed to load!');
        window.domToDataUrl = window.modernScreenshot.domToDataUrl;
        captureLogger.info('Pre-warming resource memory cache (L1)...');
        const hydrationStart = performance.now();
        const fontPromise = assetCacheManager.getAllFontData().then(allFonts => {
            for (const font of allFonts) { FONT_DATA_MEMORY_CACHE.set(font.fontUrl, font.dataUrl); }
            return allFonts.length;
        });
        const imagePromise = assetCacheManager.getAllImageData().then(allImages => {
            for (const image of allImages) { IMAGE_DATA_MEMORY_CACHE.set(image.imageUrl, image.dataUrl); }
            return allImages.length;
        });
        const [fontCount, imageCount] = await Promise.all([fontPromise, imagePromise]);
        captureLogger.info(
            `Resource cache pre-warmed. Loaded ${fontCount} fonts and ${imageCount} images. ` +
            `Took ${(performance.now() - hydrationStart).toFixed(2)} ms.`
        );
        await assetCacheManager.processFontFromStyleElement();
        setupFontChangeObserver();
        let settingsHtml = '';
        try {
            settingsHtml = await renderExtensionTemplateAsync(`third-party/${PLUGIN_ID}`, 'settings');
        } catch (ex) {
            captureLogger.error('Failed to load settings template, using fallback.', ex);
            settingsHtml = `<div id="${PLUGIN_ID}-settings"><h2>${PLUGIN_NAME} Settings</h2><p>Failed to load settings panel.</p></div>`;
        }
        $('#extensions_settings_content').append(settingsHtml);
        const settings = getPluginSettings();
        const settingsForm = $('#extensions_settings_content');
        settingsForm.find('#st_h2c_screenshotScale').val(settings.screenshotScale);
        settingsForm.find('#st_h2c_imageFormat').val(settings.imageFormat);
        settingsForm.find('#st_h2c_imageQuality').val(settings.imageQuality).prop('disabled', settings.imageFormat !== 'jpeg');
        settingsForm.find('#st_h2c_autoInstallButtons').prop('checked', settings.autoInstallButtons);
        settingsForm.find('#st_h2c_debugOverlay').prop('checked', settings.debugOverlay);
        settingsForm.on('change', 'select, input', () => {
            invalidateUnitBackgroundCache();
            const newSettings = {
                screenshotScale: parseFloat(settingsForm.find('#st_h2c_screenshotScale').val()) || defaultSettings.screenshotScale,
                imageFormat: settingsForm.find('#st_h2c_imageFormat').val(),
                imageQuality: parseFloat(settingsForm.find('#st_h2c_imageQuality').val()) || defaultSettings.imageQuality,
                autoInstallButtons: settingsForm.find('#st_h2c_autoInstallButtons').prop('checked'),
                debugOverlay: settingsForm.find('#st_h2c_debugOverlay').prop('checked'),
            };
            extension_settings[PLUGIN_ID] = newSettings;
            saveSettingsDebounced();
            settingsForm.find('#st_h2c_imageQuality').prop('disabled', newSettings.imageFormat !== 'jpeg');
            if (newSettings.autoInstallButtons) {
                installScreenshotButtons();
            } else {
                document.querySelectorAll(`.${config.buttonClass}`).forEach(btn => btn.remove());
            }
        });
        if (settings.autoInstallButtons) {
            installScreenshotButtons();
        }
        initLongScreenshotUI();
        initLogUI();
        const chatContainer = document.querySelector(config.chatContentSelector);
        if (chatContainer) {
            const resizeObserver = new ResizeObserver(() => {
                captureLogger.info('Window/container resize detected.');
                invalidateUnitBackgroundCache();
            });
            resizeObserver.observe(chatContainer);
        }
        captureLogger.info('Plugin initialized successfully.');
    } catch (error) {
        captureLogger.error('A critical error occurred during plugin initialization:', error);
    }
}

jQuery(() => {
    setTimeout(initializePlugin, 100);
});